/** @file
  Initialization functions for EFI UNDI32 driver.

Copyright (c) 2006 - 2015, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Undi32.h"
//
// Global Variables
//

PXE_SW_UNDI             *pxe_31 = NULL;  // 3.1 entry
UNDI32_DEV              *UNDI32DeviceList[MAX_NIC_INTERFACES];
UNDI_CONFIG_TABLE       *UndiDataPointer = NULL;

//
// UNDI Class Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL  gUndiDriverBinding = {
  UndiDriverSupported,
  UndiDriverStart,
  UndiDriverStop,
  0xa,
  NULL,
  NULL
};


/**
  When address mapping changes to virtual this should make the appropriate
  address conversions.

  (Standard Event handler)

  @return None

**/
VOID
EFIAPI
UndiNotifyVirtual (
  EFI_EVENT Event,
  VOID      *Context
  )
{
  UINT16  Index;
  VOID    *Pxe31Pointer;

  if (pxe_31 != NULL) {
    Pxe31Pointer = (VOID *) pxe_31;

    EfiConvertPointer (
      EFI_OPTIONAL_PTR,
      (VOID **) &Pxe31Pointer
      );

    //
    // UNDI32DeviceList is an array of pointers
    //
    for (Index = 0; Index < (pxe_31->IFcnt | pxe_31->IFcntExt << 8); Index++) {
      UNDI32DeviceList[Index]->NIIProtocol_31.Id = (UINT64) (UINTN) Pxe31Pointer;
      EfiConvertPointer (
        EFI_OPTIONAL_PTR,
        (VOID **) &(UNDI32DeviceList[Index])
        );
    }

    EfiConvertPointer (
      EFI_OPTIONAL_PTR,
      (VOID **) &(pxe_31->EntryPoint)
      );
    pxe_31 = Pxe31Pointer;
  }

  for (Index = 0; Index <= PXE_OPCODE_LAST_VALID; Index++) {
    EfiConvertPointer (
      EFI_OPTIONAL_PTR,
      (VOID **) &api_table[Index].api_ptr
      );
  }
}


/**
  When EFI is shuting down the boot services, we need to install a
  configuration table for UNDI to work at runtime!

  (Standard Event handler)

  @return None

**/
VOID
EFIAPI
UndiNotifyReadyToBoot (
  EFI_EVENT Event,
  VOID      *Context
  )
{
  InstallConfigTable ();
}


/**
  Test to see if this driver supports ControllerHandle. Any ControllerHandle
  than contains a  DevicePath, PciIo protocol, Class code of 2, Vendor ID of 0x8086,
  and DeviceId of (D100_DEVICE_ID || D102_DEVICE_ID || ICH3_DEVICE_ID_1 ||
  ICH3_DEVICE_ID_2 || ICH3_DEVICE_ID_3 || ICH3_DEVICE_ID_4 || ICH3_DEVICE_ID_5 ||
  ICH3_DEVICE_ID_6 || ICH3_DEVICE_ID_7 || ICH3_DEVICE_ID_8) can be supported.

  @param  This                 Protocol instance pointer.
  @param  Controller           Handle of device to test.
  @param  RemainingDevicePath  Not used.

  @retval EFI_SUCCESS          This driver supports this device.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
UndiDriverSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS          Status;
  EFI_PCI_IO_PROTOCOL *PciIo;
  PCI_TYPE00          Pci;

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  NULL,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint8,
                        0,
                        sizeof (PCI_CONFIG_HEADER),
                        &Pci
                        );

  if (!EFI_ERROR (Status)) {
    Status = EFI_UNSUPPORTED;

    if (Pci.Hdr.ClassCode[2] == 0x02 && Pci.Hdr.VendorId == PCI_VENDOR_ID_INTEL) {
      switch (Pci.Hdr.DeviceId) {
      case D100_DEVICE_ID:
      case D102_DEVICE_ID:
      case ICH3_DEVICE_ID_1:
      case ICH3_DEVICE_ID_2:
      case ICH3_DEVICE_ID_3:
      case ICH3_DEVICE_ID_4:
      case ICH3_DEVICE_ID_5:
      case ICH3_DEVICE_ID_6:
      case ICH3_DEVICE_ID_7:
      case ICH3_DEVICE_ID_8:
      case 0x1039:
      case 0x103A:
      case 0x103B:
      case 0x103C:
      case 0x103D:
      case 0x103E:
      case 0x1050:
      case 0x1051:
      case 0x1052:
      case 0x1053:
      case 0x1054:
      case 0x1055:
      case 0x1056:
      case 0x1057:
      case 0x1059:
      case 0x1064:
        Status = EFI_SUCCESS;
      }
    }
  }

  gBS->CloseProtocol (
        Controller,
        &gEfiPciIoProtocolGuid,
        This->DriverBindingHandle,
        Controller
        );

  return Status;
}


/**
  Start this driver on Controller by opening PciIo and DevicePath protocol.
  Initialize PXE structures, create a copy of the Controller Device Path with the
  NIC's MAC address appended to it, install the NetworkInterfaceIdentifier protocol
  on the newly created Device Path.

  @param  This                 Protocol instance pointer.
  @param  Controller           Handle of device to work with.
  @param  RemainingDevicePath  Not used, always produce all possible children.

  @retval EFI_SUCCESS          This driver is added to Controller.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
UndiDriverStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL  *UndiDevicePath;
  PCI_CONFIG_HEADER         *CfgHdr;
  UNDI32_DEV                *UNDI32Device;
  UINT16                    NewCommand;
  UINT8                     *TmpPxePointer;
  EFI_PCI_IO_PROTOCOL       *PciIoFncs;
  UINTN                     Len;
  UINT64                    Supports;
  BOOLEAN                   PciAttributesSaved;

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIoFncs,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &UndiDevicePath,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (
          Controller,
          &gEfiPciIoProtocolGuid,
          This->DriverBindingHandle,
          Controller
          );

    return Status;
  }

  PciAttributesSaved = FALSE;

  Status = gBS->AllocatePool (
                  EfiRuntimeServicesData,
                  sizeof (UNDI32_DEV),
                  (VOID **) &UNDI32Device
                  );

  if (EFI_ERROR (Status)) {
    goto UndiError;
  }

  ZeroMem ((CHAR8 *) UNDI32Device, sizeof (UNDI32_DEV));

  //
  // Get original PCI attributes
  //
  Status = PciIoFncs->Attributes (
                    PciIoFncs,
                    EfiPciIoAttributeOperationGet,
                    0,
                    &UNDI32Device->NicInfo.OriginalPciAttributes
                    );

  if (EFI_ERROR (Status)) {
    goto UndiErrorDeleteDevice;
  }
  PciAttributesSaved = TRUE;

  //
  // allocate and initialize both (old and new) the !pxe structures here,
  // there should only be one copy of each of these structure for any number
  // of NICs this undi supports. Also, these structures need to be on a
  // paragraph boundary as per the spec. so, while allocating space for these,
  // make sure that there is space for 2 !pxe structures (old and new) and a
  // 32 bytes padding for alignment adjustment (in case)
  //
  TmpPxePointer = NULL;
  if (pxe_31 == NULL) {
    Status = gBS->AllocatePool (
                    EfiRuntimeServicesData,
                    (sizeof (PXE_SW_UNDI) + sizeof (PXE_SW_UNDI) + 32),
                    (VOID **) &TmpPxePointer
                    );

    if (EFI_ERROR (Status)) {
      goto UndiErrorDeleteDevice;
    }

    ZeroMem (
      TmpPxePointer,
      sizeof (PXE_SW_UNDI) + sizeof (PXE_SW_UNDI) + 32
      );
    //
    // check for paragraph alignment here, assuming that the pointer is
    // already 8 byte aligned.
    //
    if (((UINTN) TmpPxePointer & 0x0F) != 0) {
      pxe_31 = (PXE_SW_UNDI *) ((UINTN) (TmpPxePointer + 8));
    } else {
      pxe_31 = (PXE_SW_UNDI *) TmpPxePointer;
    }

    PxeStructInit (pxe_31);
  }

  UNDI32Device->NIIProtocol_31.Id = (UINT64) (UINTN) (pxe_31);

  Status = PciIoFncs->Attributes (
                        PciIoFncs,
                        EfiPciIoAttributeOperationSupported,
                        0,
                        &Supports
                        );
  if (!EFI_ERROR (Status)) {
    Supports &= EFI_PCI_DEVICE_ENABLE;
    Status = PciIoFncs->Attributes (
                          PciIoFncs,
                          EfiPciIoAttributeOperationEnable,
                          Supports,
                          NULL
                          );
  }
  //
  // Read all the registers from device's PCI Configuration space
  //
  Status = PciIoFncs->Pci.Read (
                            PciIoFncs,
                            EfiPciIoWidthUint32,
                            0,
                            MAX_PCI_CONFIG_LEN,
                            &UNDI32Device->NicInfo.Config
                            );

  CfgHdr = (PCI_CONFIG_HEADER *) &(UNDI32Device->NicInfo.Config[0]);

  //
  // make sure that this device is a PCI bus master
  //

  NewCommand = (UINT16) (CfgHdr->Command | PCI_COMMAND_MASTER | PCI_COMMAND_IO);
  if (CfgHdr->Command != NewCommand) {
    PciIoFncs->Pci.Write (
                    PciIoFncs,
                    EfiPciIoWidthUint16,
                    PCI_COMMAND,
                    1,
                    &NewCommand
                    );
    CfgHdr->Command = NewCommand;
  }

  //
  // make sure that the latency timer is at least 32
  //
  if (CfgHdr->LatencyTimer < 32) {
    CfgHdr->LatencyTimer = 32;
    PciIoFncs->Pci.Write (
                    PciIoFncs,
                    EfiPciIoWidthUint8,
                    PCI_LATENCY_TIMER,
                    1,
                    &CfgHdr->LatencyTimer
                    );
  }
  //
  // the IfNum index for the current interface will be the total number
  // of interfaces initialized so far
  //
  UNDI32Device->NIIProtocol_31.IfNum  = pxe_31->IFcnt | pxe_31->IFcntExt << 8;

  PxeUpdate (&UNDI32Device->NicInfo, pxe_31);

  UNDI32Device->NicInfo.Io_Function                    = PciIoFncs;
  UNDI32DeviceList[UNDI32Device->NIIProtocol_31.IfNum] = UNDI32Device;
  UNDI32Device->Undi32BaseDevPath                      = UndiDevicePath;

  Status = AppendMac2DevPath (
            &UNDI32Device->Undi32DevPath,
            UNDI32Device->Undi32BaseDevPath,
            &UNDI32Device->NicInfo
            );

  if (Status != 0) {
    goto UndiErrorDeletePxe;
  }

  UNDI32Device->Signature                     = UNDI_DEV_SIGNATURE;

  UNDI32Device->NIIProtocol_31.Revision       = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION_31;
  UNDI32Device->NIIProtocol_31.Type           = EfiNetworkInterfaceUndi;
  UNDI32Device->NIIProtocol_31.MajorVer       = PXE_ROMID_MAJORVER;
  UNDI32Device->NIIProtocol_31.MinorVer       = PXE_ROMID_MINORVER_31;
  UNDI32Device->NIIProtocol_31.ImageSize      = 0;
  UNDI32Device->NIIProtocol_31.ImageAddr      = 0;
  UNDI32Device->NIIProtocol_31.Ipv6Supported  = TRUE;

  UNDI32Device->NIIProtocol_31.StringId[0]    = 'U';
  UNDI32Device->NIIProtocol_31.StringId[1]    = 'N';
  UNDI32Device->NIIProtocol_31.StringId[2]    = 'D';
  UNDI32Device->NIIProtocol_31.StringId[3]    = 'I';

  UNDI32Device->DeviceHandle                  = NULL;

  UNDI32Device->Aip.GetInformation            = UndiAipGetInfo;
  UNDI32Device->Aip.SetInformation            = UndiAipSetInfo;
  UNDI32Device->Aip.GetSupportedTypes         = UndiAipGetSupportedTypes;

  //
  // install both the 3.0 and 3.1 NII protocols.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &UNDI32Device->DeviceHandle,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  &UNDI32Device->NIIProtocol_31,
                  &gEfiDevicePathProtocolGuid,
                  UNDI32Device->Undi32DevPath,
                  &gEfiAdapterInformationProtocolGuid,
                  &UNDI32Device->Aip,
                  NULL
                  );

  if (EFI_ERROR (Status)) {
    goto UndiErrorDeleteDevicePath;
  }

  //
  // if the table exists, free it and alloc again, or alloc it directly
  //
  if (UndiDataPointer != NULL) {
    Status = gBS->FreePool(UndiDataPointer);
  }
  if (EFI_ERROR (Status)) {
    goto UndiErrorDeleteDevicePath;
  }

  Len = ((pxe_31->IFcnt|pxe_31->IFcntExt << 8)* sizeof (UndiDataPointer->NII_entry)) + sizeof (UndiDataPointer);
  Status = gBS->AllocatePool (EfiRuntimeServicesData, Len, (VOID **) &UndiDataPointer);

  if (EFI_ERROR (Status)) {
    goto UndiErrorAllocDataPointer;
  }

  //
  // Open For Child Device
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiPciIoProtocolGuid,
                  (VOID **) &PciIoFncs,
                  This->DriverBindingHandle,
                  UNDI32Device->DeviceHandle,
                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                  );

  return EFI_SUCCESS;
UndiErrorAllocDataPointer:
  gBS->UninstallMultipleProtocolInterfaces (
                  &UNDI32Device->DeviceHandle,
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  &UNDI32Device->NIIProtocol_31,
                  &gEfiDevicePathProtocolGuid,
                  UNDI32Device->Undi32DevPath,
                  &gEfiAdapterInformationProtocolGuid,
                  &UNDI32Device->Aip,
                  NULL
                  );

UndiErrorDeleteDevicePath:
  UNDI32DeviceList[UNDI32Device->NIIProtocol_31.IfNum] = NULL;
  gBS->FreePool (UNDI32Device->Undi32DevPath);

UndiErrorDeletePxe:
  PxeUpdate (NULL, pxe_31);
  if (TmpPxePointer != NULL) {
    gBS->FreePool (TmpPxePointer);

  }

UndiErrorDeleteDevice:
  if (PciAttributesSaved) {
    //
    // Restore original PCI attributes
    //
    PciIoFncs->Attributes (
                    PciIoFncs,
                    EfiPciIoAttributeOperationSet,
                    UNDI32Device->NicInfo.OriginalPciAttributes,
                    NULL
                    );
  }

  gBS->FreePool (UNDI32Device);

UndiError:
  gBS->CloseProtocol (
        Controller,
        &gEfiDevicePathProtocolGuid,
        This->DriverBindingHandle,
        Controller
        );

  gBS->CloseProtocol (
        Controller,
        &gEfiPciIoProtocolGuid,
        This->DriverBindingHandle,
        Controller
        );

  return Status;
}


/**
  Stop this driver on Controller by removing NetworkInterfaceIdentifier protocol and
  closing the DevicePath and PciIo protocols on Controller.

  @param  This                 Protocol instance pointer.
  @param  Controller           Handle of device to stop driver on.
  @param  NumberOfChildren     How many children need to be stopped.
  @param  ChildHandleBuffer    Not used.

  @retval EFI_SUCCESS          This driver is removed Controller.
  @retval other                This driver was not removed from this device.

**/
// TODO:    EFI_DEVICE_ERROR - add return value to function comment
EFI_STATUS
EFIAPI
UndiDriverStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN  EFI_HANDLE                     Controller,
  IN  UINTN                          NumberOfChildren,
  IN  EFI_HANDLE                     *ChildHandleBuffer
  )
{
  EFI_STATUS                                Status;
  BOOLEAN                                   AllChildrenStopped;
  UINTN                                     Index;
  UNDI32_DEV                                *UNDI32Device;
  EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *NIIProtocol;

  //
  // Complete all outstanding transactions to Controller.
  // Don't allow any new transaction to Controller to be started.
  //
  if (NumberOfChildren == 0) {

    //
    // Close the bus driver
    //
    Status = gBS->CloseProtocol (
                    Controller,
                    &gEfiDevicePathProtocolGuid,
                    This->DriverBindingHandle,
                    Controller
                    );

    Status = gBS->CloseProtocol (
                    Controller,
                    &gEfiPciIoProtocolGuid,
                    This->DriverBindingHandle,
                    Controller
                    );

    return Status;
  }

  AllChildrenStopped = TRUE;

  for (Index = 0; Index < NumberOfChildren; Index++) {

    Status = gBS->OpenProtocol (
                    ChildHandleBuffer[Index],
                    &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                    (VOID **) &NIIProtocol,
                    This->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (!EFI_ERROR (Status)) {

      UNDI32Device = UNDI_DEV_FROM_THIS (NIIProtocol);

      Status = gBS->CloseProtocol (
                      Controller,
                      &gEfiPciIoProtocolGuid,
                      This->DriverBindingHandle,
                      ChildHandleBuffer[Index]
                      );
      if (!EFI_ERROR (Status)) {
        Status = gBS->UninstallMultipleProtocolInterfaces (
                        ChildHandleBuffer[Index],
                        &gEfiDevicePathProtocolGuid,
                        UNDI32Device->Undi32DevPath,
                        &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                        &UNDI32Device->NIIProtocol_31,
                        NULL
                        );
        if (!EFI_ERROR (Status)) {
          //
          // Restore original PCI attributes
          //
          Status = UNDI32Device->NicInfo.Io_Function->Attributes (
                                                        UNDI32Device->NicInfo.Io_Function,
                                                        EfiPciIoAttributeOperationSet,
                                                        UNDI32Device->NicInfo.OriginalPciAttributes,
                                                        NULL
                                                        );

          ASSERT_EFI_ERROR (Status);

          gBS->FreePool (UNDI32Device->Undi32DevPath);
          gBS->FreePool (UNDI32Device);

        }
      }
    }

    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (!AllChildrenStopped) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;

}


/**
  Use the EFI boot services to produce a pause. This is also the routine which
  gets replaced during RunTime by the O/S in the NIC_DATA_INSTANCE so it can
  do it's own pause.

  @param  UnqId                Runtime O/S routine might use this, this temp
                               routine does not use it
  @param  MicroSeconds         Determines the length of pause.

  @return none

**/
VOID
TmpDelay (
  IN UINT64 UnqId,
  IN UINTN  MicroSeconds
  )
{
  gBS->Stall ((UINT32) MicroSeconds);
}


/**
  Use the PCI IO abstraction to issue memory or I/O reads and writes.  This is also the routine which
  gets replaced during RunTime by the O/S in the NIC_DATA_INSTANCE so it can do it's own I/O abstractions.

  @param  UnqId                Runtime O/S routine may use this field, this temp
                               routine does not.
  @param  ReadWrite            Determine if it is an I/O or Memory Read/Write
                               Operation.
  @param  Len                  Determines the width of the data operation.
  @param  Port                 What port to Read/Write from.
  @param  BuffAddr             Address to read to or write from.

  @return none

**/
VOID
TmpMemIo (
  IN UINT64 UnqId,
  IN UINT8  ReadWrite,
  IN UINT8  Len,
  IN UINT64 Port,
  IN UINT64 BuffAddr
  )
{
  EFI_PCI_IO_PROTOCOL_WIDTH Width;
  NIC_DATA_INSTANCE         *AdapterInfo;

  Width       = (EFI_PCI_IO_PROTOCOL_WIDTH) 0;
  AdapterInfo = (NIC_DATA_INSTANCE *) (UINTN) UnqId;
  switch (Len) {
  case 2:
    Width = (EFI_PCI_IO_PROTOCOL_WIDTH) 1;
    break;

  case 4:
    Width = (EFI_PCI_IO_PROTOCOL_WIDTH) 2;
    break;

  case 8:
    Width = (EFI_PCI_IO_PROTOCOL_WIDTH) 3;
    break;
  }

  switch (ReadWrite) {
  case PXE_IO_READ:
    AdapterInfo->Io_Function->Io.Read (
                                  AdapterInfo->Io_Function,
                                  Width,
                                  1,
                                  Port,
                                  1,
                                  (VOID *) (UINTN) (BuffAddr)
                                  );
    break;

  case PXE_IO_WRITE:
    AdapterInfo->Io_Function->Io.Write (
                                  AdapterInfo->Io_Function,
                                  Width,
                                  1,
                                  Port,
                                  1,
                                  (VOID *) (UINTN) (BuffAddr)
                                  );
    break;

  case PXE_MEM_READ:
    AdapterInfo->Io_Function->Mem.Read (
                                    AdapterInfo->Io_Function,
                                    Width,
                                    0,
                                    Port,
                                    1,
                                    (VOID *) (UINTN) (BuffAddr)
                                    );
    break;

  case PXE_MEM_WRITE:
    AdapterInfo->Io_Function->Mem.Write (
                                    AdapterInfo->Io_Function,
                                    Width,
                                    0,
                                    Port,
                                    1,
                                    (VOID *) (UINTN) (BuffAddr)
                                    );
    break;
  }

  return ;
}


/**
  Using the NIC data structure information, read the EEPROM to get the MAC address and then allocate space
  for a new devicepath (**DevPtr) which will contain the original device path the NIC was found on (*BaseDevPtr)
  and an added MAC node.

  @param  DevPtr               Pointer which will point to the newly created device
                               path with the MAC node attached.
  @param  BaseDevPtr           Pointer to the device path which the UNDI device
                               driver is latching on to.
  @param  AdapterInfo          Pointer to the NIC data structure information which
                               the UNDI driver is layering on..

  @retval EFI_SUCCESS          A MAC address was successfully appended to the Base
                               Device Path.
  @retval other                Not enough resources available to create new Device
                               Path node.

**/
EFI_STATUS
AppendMac2DevPath (
  IN OUT  EFI_DEVICE_PATH_PROTOCOL **DevPtr,
  IN      EFI_DEVICE_PATH_PROTOCOL *BaseDevPtr,
  IN      NIC_DATA_INSTANCE        *AdapterInfo
  )
{
  EFI_MAC_ADDRESS           MACAddress;
  PCI_CONFIG_HEADER         *CfgHdr;
  INT32                     Val;
  INT32                     Index;
  INT32                     Index2;
  UINT8                     AddrLen;
  MAC_ADDR_DEVICE_PATH      MacAddrNode;
  EFI_DEVICE_PATH_PROTOCOL  *EndNode;
  UINT8                     *DevicePtr;
  UINT16                    TotalPathLen;
  UINT16                    BasePathLen;
  EFI_STATUS                Status;

  //
  // set the environment ready (similar to UNDI_Start call) so that we can
  // execute the other UNDI_ calls to get the mac address
  // we are using undi 3.1 style
  //
  AdapterInfo->Delay      = TmpDelay;
  AdapterInfo->Virt2Phys  = (VOID *) 0;
  AdapterInfo->Block      = (VOID *) 0;
  AdapterInfo->Map_Mem    = (VOID *) 0;
  AdapterInfo->UnMap_Mem  = (VOID *) 0;
  AdapterInfo->Sync_Mem   = (VOID *) 0;
  AdapterInfo->Mem_Io     = TmpMemIo;
  //
  // these tmp call-backs follow 3.1 undi style
  // i.e. they have the unique_id parameter.
  //
  AdapterInfo->VersionFlag  = 0x31;
  AdapterInfo->Unique_ID    = (UINT64) (UINTN) AdapterInfo;

  //
  // undi init portion
  //
  CfgHdr              = (PCI_CONFIG_HEADER *) &(AdapterInfo->Config[0]);
  AdapterInfo->ioaddr = 0;
  AdapterInfo->RevID  = CfgHdr->RevID;

  AddrLen             = E100bGetEepromAddrLen (AdapterInfo);

  for (Index = 0, Index2 = 0; Index < 3; Index++) {
    Val                       = E100bReadEeprom (AdapterInfo, Index, AddrLen);
    MACAddress.Addr[Index2++] = (UINT8) Val;
    MACAddress.Addr[Index2++] = (UINT8) (Val >> 8);
  }

  SetMem (MACAddress.Addr + Index2, sizeof (EFI_MAC_ADDRESS) - Index2, 0);
  //for (; Index2 < sizeof (EFI_MAC_ADDRESS); Index2++) {
  //  MACAddress.Addr[Index2] = 0;
  //}
  //
  // stop undi
  //
  AdapterInfo->Delay  = (VOID *) 0;
  AdapterInfo->Mem_Io = (VOID *) 0;

  //
  // fill the mac address node first
  //
  ZeroMem ((CHAR8 *) &MacAddrNode, sizeof MacAddrNode);
  CopyMem (
    (CHAR8 *) &MacAddrNode.MacAddress,
    (CHAR8 *) &MACAddress,
    sizeof (EFI_MAC_ADDRESS)
    );

  MacAddrNode.Header.Type       = MESSAGING_DEVICE_PATH;
  MacAddrNode.Header.SubType    = MSG_MAC_ADDR_DP;
  MacAddrNode.Header.Length[0]  = (UINT8) sizeof (MacAddrNode);
  MacAddrNode.Header.Length[1]  = 0;

  //
  // find the size of the base dev path.
  //
  EndNode = BaseDevPtr;

  while (!IsDevicePathEnd (EndNode)) {
    EndNode = NextDevicePathNode (EndNode);
  }

  BasePathLen = (UINT16) ((UINTN) (EndNode) - (UINTN) (BaseDevPtr));

  //
  // create space for full dev path
  //
  TotalPathLen = (UINT16) (BasePathLen + sizeof (MacAddrNode) + sizeof (EFI_DEVICE_PATH_PROTOCOL));

  Status = gBS->AllocatePool (
                  EfiRuntimeServicesData,
                  TotalPathLen,
                  (VOID **) &DevicePtr
                  );

  if (Status != EFI_SUCCESS) {
    return Status;
  }
  //
  // copy the base path, mac addr and end_dev_path nodes
  //
  *DevPtr = (EFI_DEVICE_PATH_PROTOCOL *) DevicePtr;
  CopyMem (DevicePtr, (CHAR8 *) BaseDevPtr, BasePathLen);
  DevicePtr += BasePathLen;
  CopyMem (DevicePtr, (CHAR8 *) &MacAddrNode, sizeof (MacAddrNode));
  DevicePtr += sizeof (MacAddrNode);
  CopyMem (DevicePtr, (CHAR8 *) EndNode, sizeof (EFI_DEVICE_PATH_PROTOCOL));

  return EFI_SUCCESS;
}


/**
  Install a GUID/Pointer pair into the system's configuration table.

  none

  @retval EFI_SUCCESS          Install a GUID/Pointer pair into the system's
                               configuration table.
  @retval other                Did not successfully install the GUID/Pointer pair
                               into the configuration table.

**/
// TODO:    VOID - add argument and description to function comment
EFI_STATUS
InstallConfigTable (
  IN VOID
  )
{
  EFI_STATUS              Status;
  EFI_CONFIGURATION_TABLE *CfgPtr;
  UNDI_CONFIG_TABLE       *TmpData;
  UINT16                  Index;
  UNDI_CONFIG_TABLE       *UndiData;

  if (pxe_31 == NULL) {
    return EFI_SUCCESS;
  }

  if(UndiDataPointer == NULL) {
    return EFI_SUCCESS;
  }

  UndiData = (UNDI_CONFIG_TABLE *)UndiDataPointer;

  UndiData->NumberOfInterfaces  = (pxe_31->IFcnt | pxe_31->IFcntExt << 8);
  UndiData->nextlink            = NULL;

  for (Index = 0; Index < (pxe_31->IFcnt | pxe_31->IFcntExt << 8); Index++) {
    UndiData->NII_entry[Index].NII_InterfacePointer = &UNDI32DeviceList[Index]->NIIProtocol_31;
    UndiData->NII_entry[Index].DevicePathPointer    = UNDI32DeviceList[Index]->Undi32DevPath;
  }

  //
  // see if there is an entry in the config table already
  //
  CfgPtr = gST->ConfigurationTable;

  for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
    Status = CompareGuid (
              &CfgPtr->VendorGuid,
              &gEfiNetworkInterfaceIdentifierProtocolGuid_31
              );
    if (Status != EFI_SUCCESS) {
      break;
    }

    CfgPtr++;
  }

  if (Index < gST->NumberOfTableEntries) {
    TmpData = (UNDI_CONFIG_TABLE *) CfgPtr->VendorTable;

    //
    // go to the last link
    //
    while (TmpData->nextlink != NULL) {
      TmpData = TmpData->nextlink;
    }

    TmpData->nextlink = UndiData;

    //
    // 1st one in chain
    //
    UndiData = (UNDI_CONFIG_TABLE *) CfgPtr->VendorTable;
  }

  //
  // create an entry in the configuration table for our GUID
  //
  Status = gBS->InstallConfigurationTable (
                  &gEfiNetworkInterfaceIdentifierProtocolGuid_31,
                  UndiData
                  );
  return Status;
}

/**

**/
EFI_STATUS
EFIAPI
InitializeUndi(
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_EVENT     Event;
  EFI_STATUS    Status;

  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gUndiDriverBinding,
             ImageHandle,
             &gUndiComponentName,
             &gUndiComponentName2
             );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  UndiNotifyReadyToBoot,
                  NULL,
                  &gEfiEventReadyToBootGuid,
                  &Event
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  UndiNotifyVirtual,
                  NULL,
                  &gEfiEventVirtualAddressChangeGuid,
                  &Event
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}
